Archivos .h y .c

Ejemplos de que como y cuando

Ejemplo 1

La forma mas sencilla de agregar codigo que tenemos en otro archivo es incluyendo directamente el ".c" del mismo. Esto a priori funciona, pero no es una muy buena idea:


In [1]:
cat ./english_greeter.c


#include <stdio.h>

void greet(const char* name){
	printf("Hello %s\n",name);
}

In [2]:
cat ./first_example.c


#include "english_greeter.c"

int main(){
	greet("Richard");
	return 0;
}

In [3]:
gcc -Wall -pedantic first_example.c --std=c99 -o example



Como vemos, compila sin errores


In [4]:
./example


Hello Richard

Pero eso tiene varios problemas. Por ejemplo, estamos agregando a nuestro codigo cosas que no nos interesan. Mas aun, cosas de la implementacion pueden entrar en conflicto con cosas de nuestro programa. En este caso, la definicion de los macros "MESSAGE" conflictuan entre si.


In [5]:
cat ./english_greeter2.c


#include <stdio.h>

#define MESSAGE "Hello %s\n"

void greet(const char* name){
	printf(MESSAGE,name);
}

In [6]:
cat ./first_example_b.c


#include "english_greeter2.c"

#define MESSAGE "Something"

int main(){
	greet("Richard");
	return 0;
}

In [7]:
gcc -Wall -pedantic first_example_b.c --std=c99 -o example


first_example_b.c:3:0: warning: "MESSAGE" redefined
 #define MESSAGE "Something"
 
In file included from first_example_b.c:1:0:
english_greeter2.c:3:0: note: this is the location of the previous definition
 #define MESSAGE "Hello %s\n"
 

Podemos ver en este caso como ya eso no compila.

Ejemplo 2

La forma correcta de hacerlo es utilizando archivos header o .h. Por ejemplo


In [8]:
cat ./second_example.c


#include "greeter.h"

int main(){
	greet("Richard");
	return 0;
}

In [9]:
cat ./greeter.h


#ifndef GREETER
#define GREETER

void greet(const char* name);

#endif

In [10]:
cat ./greeter.c


#include <stdio.h>

void greet(const char* name){
	printf("Hello %s\n",name);
}

In [11]:
gcc -Wall -pedantic second_example.c --std=c99 -o example


/tmp/ccwSPEDI.o: In function `main':
second_example.c:(.text+0xa): undefined reference to `greet'
collect2: error: ld returned 1 exit status

La compilacion de esa manera falla. El compilador sabe que hay una funcion greet pero no que hace. ¿Por que? porque al incluir el .h es como si definieramos en nuestro .c solo el prototipo de la funcion greet. Notar que no es el mismo error que si no estuviese la definicion de la funcion:


In [12]:
cat ./second_example_b.c


int main(){
	greet("Richard");
	return 0;
}

In [13]:
gcc -Wall -pedantic second_example_b.c --std=c99 -o example


second_example_b.c: In function 'main':
second_example_b.c:2:2: warning: implicit declaration of function 'greet' [-Wimplicit-function-declaration]
  greet("Richard");
  ^~~~~
/tmp/ccEommY3.o: In function `main':
second_example_b.c:(.text+0xf): undefined reference to `greet'
collect2: error: ld returned 1 exit status

Para poder utilizar nuestros archivos, primero debemos generar el codigo objeto del mismo. En este caso:


In [14]:
cat ./greeter.h


#ifndef GREETER
#define GREETER

void greet(const char* name);

#endif

In [15]:
cat ./greeter.c


#include <stdio.h>

void greet(const char* name){
	printf("Hello %s\n",name);
}

In [16]:
gcc -Wall -pedantic greeter.c --std=c99 -c



El parametro -c le dice al compilador que genere el codigo objeto del archivo que estamos pasando. Esto nos va a generar un archivo .o. Ahora solo resta decirle al compilador que lo pegue a nuestro codigo principal:


In [17]:
gcc -Wall -pedantic second_example.c greeter.o --std=c99 -o example




In [18]:
./example


Hello Richard

Ejemplo 3

A continuacion, un pequeño ejemplo de como tener separada la implementacion de la definicion nos puede ser util:


In [24]:
cat ./third_example.c


#include "greeter.h"

int main(){
	greet("Richard");
	return 0;
}

In [25]:
cat ./greeter.h


#ifndef GREETER
#define GREETER

void greet(const char* name);

#endif

Ahora vamos a generar codigo objeto de nuestro greeter en ingles:


In [26]:
cat ./english_greeter.c


#include <stdio.h>

void greet(const char* name){
	printf("Hello %s\n",name);
}

In [27]:
gcc -Wall -pedantic english_greeter.c --std=c99 -c -o greeter.o




In [28]:
gcc -Wall -pedantic third_example.c greeter.o --std=c99 -o example




In [29]:
./example


Hello Richard

Notar que compilamos english_greeter.c en un archivo llamado greeter.o. El codigo objeto no necesariamente se tiene que llamar igual que el archivo fuente. Ahora supongamos que quiero cambiar el idioma de mi programa. Bueno, simplemente compilo utilizando un greeter diferente, por ejemplo uno en español:


In [30]:
cat ./spanish_greeter.c


#include <stdio.h>

void greet(const char* name){
	printf("Hola %s\n",name);
}

In [31]:
gcc -Wall -pedantic spanish_greeter.c --std=c99 -c -o greeter.o




In [32]:
gcc -Wall -pedantic third_example.c greeter.o --std=c99 -o example




In [33]:
./example


Hola Richard

Esto nos permite cambiar facilmente entre una u otra implementacion. Uno podria por ejemlo distribuir los fuentes de este programa y que el usuario compile el programa en un idioma u otro. ¿Como hacer esto? Proximamente en clase...